/*******************************************************************************
 * Copyright (c) 2001-2003 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core.syncinfo;

import java.io.Externalizable;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;

import net.sourceforge.perforce.core.internal.Policy;

/**
  * @version $Revision: 244 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class ResourceSyncInfo implements Externalizable
{
	// ---------------------------------------------------------------------------- Public Constants
	
	/** Indicating non existing resource sync info. */
	public static final ResourceSyncInfo NULL_INFO = new ResourceSyncInfo(true);	
	/** Constant for the add action. */
	public static final String ACTION_ADD    = "add";                                  //$NON-NLS-1$
	/** Constant for the branch action. */
	public static final String ACTION_BRANCH = "branch";                               //$NON-NLS-1$
	/** Constant for the delete action. */
	public static final String ACTION_DELETE = "delete";                               //$NON-NLS-1$
	/** Constant of the edit action. */
	public static final String ACTION_EDIT   = "edit";                                 //$NON-NLS-1$
	
	// ----------------------------------------------------------------------------------- Constants

	/** serial number of the class. */	
	static final long serialVersionUID = -5903762684861295448L;

	// --------------------------------------------------------------------------- Private Constants 

	/** Constant: Resource is resolved */
	private static final int FLAG_RESOLVED    = 0x00000001;
	/** Constant: Resource is opened by someone else. */
	private static final int FLAG_OTHER_OPEN  = 0x00000002;
	/** Constants: Resource is locked by us. */
	private static final int FLAG_OUR_LOCK    = 0x00000004;
	/** Constant: Have revision is added. */
	private static final int FLAG_HAVE_ADD    = 0x00000010;
	/** Constant: Have revision is branched. */
	private static final int FLAG_HAVE_BRANCH = 0x00000020;
	/** Constant: Have revision is deleted. */
	private static final int FLAG_HAVE_DELETE = 0x00000040;
	/** Constant: Have revision is opened for edit. */
	private static final int FLAG_HAVE_EDIT   = 0x00000080;
	/** Constant: Head revision was added. */
	private static final int FLAG_HEAD_ADD    = 0x00000100;
	/** Constant: Head revision was branched. */
	private static final int FLAG_HEAD_BRANCH = 0x00000200;
	/** Constant: Head revision was deleted. */
	private static final int FLAG_HEAD_DELETE = 0x00000400;
	/** Constant: Head revision was edited. */
	private static final int FLAG_HEAD_EDIT   = 0x00000800;
	/** Constant: Marks the NULL sync info */
	private static final int FLAG_NULL_INFO   = 0x10000000;
	
	/** Constant: Bit mask including all 'have' states. */
	private static final int FLAG_HAVE_ALL    = 0x000000f0;
	/** Constant: Bit mask including all 'head' states. */
	private static final int FLAG_HEAD_ALL    = 0x00000f00;	

	// --------------------------------------------------------------------------- Private Variables

	/** Stores a creation timestamp. */
	private long timestamp = System.currentTimeMillis();
	/** Stores the client filename. */
	private String clientFileName = "";                                                //$NON-NLS-1$
	/** Stores the depot filename. */
	private String depotFileName = "";                                                 //$NON-NLS-1$
	/** Stores the head type. */
	private String headType = "";                                                      //$NON-NLS-1$
	/** Stores the head time. */
	private Date headTime = new Date();
	/** Stores the head revision number. */
	private int headRev = 0;
	/** Stores the head changelist number. */
	private int headChange = 0;
	/** Stores the client changelist number. */
	private int change;
	/** Stores the have revision. */
	private int haveRev = 0;
	/** Indicates whether the file is resolved. */
	private int flags = FLAG_RESOLVED;

	// ------------------------------------------------------------------------- Public Constructors

	/** Constructor for ResourceSyncInfo. */
	public ResourceSyncInfo()
	{
	}
	
	// ------------------------------------------------------------------------ Private Constructors
	
	/** Constructor for ResourceSyncInfo.
	  * 
	  * @param isNull indicates whether we want to create a null sync info.
	  */
	private ResourceSyncInfo(boolean isNull)
	{
		if (isNull)
		{
			flags |= FLAG_NULL_INFO;
		}
	}

	// ------------------------------------------------------------------------------ Public Methods
	
	/** Returns the entry's timestamp.
	  *
	  * @return the timestamp. 
	  */ 
	public long getTimestamp()
	{
		return timestamp;
	}
	
	/** Gets the action.
	  * 
	  * @return Returns a String
	  */
	public String getAction()
	{
		switch (flags & FLAG_HAVE_ALL)
		{
			case 0:
				return "";
				
			case FLAG_HAVE_ADD:
				return ACTION_ADD;
				
			case FLAG_HAVE_BRANCH:
				return ACTION_BRANCH;
				
			case FLAG_HAVE_DELETE:
				return ACTION_DELETE;
				
			case FLAG_HAVE_EDIT:
				return ACTION_EDIT;
				
			default:
				throw new IllegalStateException();
		}		
	}

	/** Sets the action.
	  * 
	  * @param action The action to set
	  */
	public void setAction(String action)
	{
		Policy.assertNotNull(action);
		
		if (action.equals(ACTION_ADD))
		{
			flags &= ~FLAG_HAVE_ALL;
			flags |= FLAG_HAVE_ADD;
		}
		else
		if (action.equals(ACTION_BRANCH))
		{
			flags &= ~FLAG_HAVE_ALL;
			flags |= FLAG_HAVE_BRANCH;
		}
		else
		if (action.equals(ACTION_DELETE))
		{
			flags &= ~FLAG_HAVE_ALL;
			flags |= FLAG_HAVE_DELETE;
		}
		else
		if (action.equals(ACTION_EDIT))
		{
			flags &= ~FLAG_HAVE_ALL;
			flags |= FLAG_HAVE_EDIT;
		}
		else
		{
			flags &= ~FLAG_HAVE_ALL;
		}
	}

	/** Gets the depotFileName.
	  * 
	  * @return Returns a String
	  */
	public String getClientFileName()
	{
		return clientFileName;
	}

	/** Sets the clientFileName.
	  * 
	  * @param clientFileName The clientFileName to set
	  */
	public void setClientFileName(String clientFileName)
	{
		Policy.assertNotNull(clientFileName);
		this.clientFileName = clientFileName;
	}

	/** Gets the depotFileName.
	  * 
	  * @return Returns a String
	  */
	public String getDepotFileName()
	{
		return depotFileName;
	}

	/** Sets the depotFileName.
	  * 
	  * @param depotFileName The depotFileName to set
	  */
	public void setDepotFileName(String depotFileName)
	{
		Policy.assertNotNull(depotFileName);
		this.depotFileName = depotFileName;
	}

	/** Gets the headAction.
	  * 
	  * @return the head action.
	  */	
	public String getHeadAction()
	{
		switch (flags & FLAG_HEAD_ALL)
		{
			case 0:
				return "";
				
			case FLAG_HEAD_ADD:
				return ACTION_ADD;
				
			case FLAG_HEAD_BRANCH:
				return ACTION_BRANCH;
				
			case FLAG_HEAD_DELETE:
				return ACTION_DELETE;
				
			case FLAG_HEAD_EDIT:
				return ACTION_EDIT;
				
			default:
				throw new IllegalStateException();
		}		
	}
	
	/** Sets the headAction.
	  * 
	  * @param headAction The headAction to set
	  */
	public void setHeadAction(String action)
	{
		Policy.assertNotNull(action);
				
		if (action.equals(ACTION_ADD))
		{
			flags &= ~FLAG_HEAD_ALL;
			flags |= FLAG_HEAD_ADD;
		}
		else
		if (action.equals(ACTION_BRANCH))
		{
			flags &= ~FLAG_HEAD_ALL;
			flags |= FLAG_HEAD_BRANCH;
		}
		else
		if (action.equals(ACTION_DELETE))
		{
			flags &= ~FLAG_HEAD_ALL;
			flags |= FLAG_HEAD_DELETE;
		}
		else
		if (action.equals(ACTION_EDIT))
		{
			flags &= ~FLAG_HEAD_ALL;
			flags |= FLAG_HEAD_EDIT;
		}
		else
		{
			flags &= ~FLAG_HEAD_ALL;
		}
	}
	
	/** Gets the headType.
	  * 
	  * @return Returns a String
	  */
	public String getHeadType()
	{
		return headType;
	}

	/** Sets the headType.
	  * 
	  * @param headType The headType to set
	  */
	public void setHeadType(String headType)
	{
		Policy.assertNotNull(headType);		
		this.headType = headType;
	}

	/** Gets the headRev.
	  * @return Returns a Integer
	  */
	public int getHeadRev()
	{
		return headRev;
	}

	/** Sets the headRev.
	  * 
	  * @param headRev The headRev to set
	  */
	public void setHeadRev(int headRev)
	{
		this.headRev = headRev;
	}

	/** Gets the headChange.
	  * 
	  * @return Returns a Integer
	  */
	public int getHeadChange()
	{
		return headChange;
	}

	/** Sets the headChange.
	  * 
	  * @param headChange The headChange to set
	  */
	public void setHeadChange(int headChange)
	{
		this.headChange = headChange;
	}
	
	/** Gets the change.
	  *
	  * @return return an Integer. 
	  */
	public int getChange()
	{
		return change;
	}
	
	/** Sets the change.
	  *
	  * @param change the new change value. 
	  */
	public void setChange(int change)
	{		
		this.change = change;
	}

	/** Gets the haveRev.
	  * 
	  * @return Returns a Integer
	  */
	public int getHaveRev()
	{
		return haveRev;
	}

	/** Sets the haveRev.
	  * 
	  * @param haveRev The haveRev to set
	  */
	public void setHaveRev(int haveRev)
	{
		this.haveRev = haveRev;
	}

	/** Gets the head time.
	  * 	
	  * @return the head time.
	  */
	public Date getHeadTime()
	{
		return headTime;
	}
	
	/** Sets the head time.
	  * 
	  * @param headTime the new head time.
	  */
	public void setHeadTime(Date headTime)
	{
		Policy.assertNotNull(headTime);
		this.headTime = headTime;
	}

	/** Checks whether the file is resolved.
	  * 
	  * @return true if resolved.
	  */
	public boolean isResolved()
	{
		return (flags & FLAG_RESOLVED) > 0;
	}
	
	/** Sets whether the file is resolved.
	  * 
	  * @param resolved the new resolved value.
	  */
	public void setResolved(boolean resolved)
	{
		if (resolved)
		{
			this.flags |= FLAG_RESOLVED;
		}
		else
		{
			this.flags &= ~FLAG_RESOLVED;
		}
	}

	/** Checks whether the file is opened by another one.
	  * 
	  * @return true or false.
	  */
	public boolean isOtherOpen()
	{
		return (flags & FLAG_OTHER_OPEN) > 0; 
	}
	
	/** Sets the otherOpen value.
	  * 
	  * @param otherOpen true or false.
	  */
	public void setOtherOpen(boolean otherOpen)
	{
		if (otherOpen)
		{
			this.flags |= FLAG_OTHER_OPEN;
		}
		else
		{
			this.flags &= ~FLAG_OTHER_OPEN;
		}
	}

	/** Returns if the resource is locked by us or not.
	  *
	  * @return true or false. 
	  */
	public boolean hasOurLock()
	{
		return (flags & FLAG_OUR_LOCK) > 0;
	}
	
	/** Changes the value that indicates whether the resource is locked locally
	  * or not.
	  * 
	  * @param ourLock new value 
	  */
	public void setHasOurLock(boolean ourLock)
	{
		if (ourLock)
		{
			this.flags |= FLAG_OUR_LOCK;
		}
		else
		{
			this.flags &= ~FLAG_OUR_LOCK;
		}
	}

	/** Checks whether this is a NULL resource sync info. 
	  * 
	  * @return true if this is a null info.
	  */
	public boolean isNull()
	{
		return (flags & FLAG_NULL_INFO) > 0;
	}	

	/** Returns the name of the resource.
	  * 
	  * @return the resource's name.
	  */
	public String getName()
	{
		String name = getClientFileName();
		return name.substring(name.lastIndexOf(File.separator) + 1);
	}
	
	public boolean isAdded()
	{
		return (flags & FLAG_HAVE_ADD) > 0;
	}
	
	public boolean isBranched()
	{
		return (flags & FLAG_HAVE_BRANCH) > 0;
	}
	
	/** Checks whether the resource is deleted. 
	  * 
	  * @return true if resource is deleted.
	  */
	public boolean isDeleted()
	{
		if ((flags & FLAG_HAVE_ADD) > 0 || (flags & FLAG_HAVE_BRANCH) > 0)
		{
			return false;
		}
		
		return haveRev == 0 ||
		       ((headRev == haveRev) && 
		        (flags & FLAG_HEAD_DELETE) > 0 && 
		        (flags & FLAG_HAVE_ALL) == 0);
	}
	
	public boolean isEdited()
	{
		return (flags & FLAG_HAVE_EDIT) > 0;
	}
	
	/** @see java.lang.Object#equals(java.lang.Object) */
	public boolean equals(Object other) 
	{
		if (this == other)
		{
			return true;
		}
			
			
		if (!(other instanceof ResourceSyncInfo))
		{
			return false;
		}

		ResourceSyncInfo otherSyncInfo = (ResourceSyncInfo) other;
		if ((headType.equals(otherSyncInfo.headType)) &&
		    (headTime.equals(otherSyncInfo.headTime)) &&
		    (headRev == otherSyncInfo.headRev) &&
		    (headChange == otherSyncInfo.headChange) &&
		    (change == otherSyncInfo.change) &&
		    (haveRev == otherSyncInfo.haveRev) &&
		    (flags == otherSyncInfo.flags) &&
		    (clientFileName.equals(otherSyncInfo.clientFileName)) &&
		    (depotFileName.equals(otherSyncInfo.depotFileName)))
		{
			return true;
		}
		return false; 
	}
		
	// ------------------------------------------------------------- Public Methods (Externalizable)
	
	/** @see java.io.Externalizable#readExternal(java.io.ObjectInput) */
	public void readExternal(ObjectInput in) 
		throws IOException 
	{
		timestamp = in.readLong();
		clientFileName = in.readUTF();
		depotFileName = in.readUTF();
		headType = in.readUTF();
		headTime = new Date(in.readLong());
		headRev = in.readInt();
		headChange = in.readInt();
		
		change = in.readInt();
		haveRev = in.readInt();
		flags = in.readInt();
	}

	/** @see java.io.Externalizable#writeExternal(java.io.ObjectOutput) */
	public void writeExternal(ObjectOutput out) 
		throws IOException 
	{
		out.writeLong(timestamp);
		out.writeUTF(clientFileName);
		out.writeUTF(depotFileName);
		out.writeUTF(headType);
		out.writeLong(headTime.getTime());
		out.writeInt(headRev);
		out.writeInt(headChange);
		out.writeInt(change);
		out.writeInt(haveRev);
		out.writeInt(flags);
	}
}
